﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;

namespace Athena.ProxyGenerator.Implementaions
{
    using Correlation;
    using Convertible;
    using Runtime.Client;
    public class ServiceProxyGenerater : IServiceProxyGenerater, IDisposable
    {
        private const string _proxyClassNameFormat = "ClientProxy";
        private readonly IServiceCorrelationIdGenerator _serviceCorrelationIdGenerator;
        private readonly ILogger<ServiceProxyGenerater> _logger;
        private SortedSet<string> _namespaceCache;

        public ServiceProxyGenerater(IServiceCorrelationIdGenerator serviceCorrelationIdGenerator, ILogger<ServiceProxyGenerater> logger)
        {
            _serviceCorrelationIdGenerator = serviceCorrelationIdGenerator;
            _logger = logger;
        }

        /// <summary>
        /// 动态生成代理类
        /// </summary>
        /// <param name="interfaceTypes"></param>
        /// <returns></returns>
        public IEnumerable<Type> GeneratorProxys(IEnumerable<Type> interfaceTypes)
        {
            var assemblys = DependencyContext.Default.RuntimeLibraries.SelectMany(x => x.GetDefaultAssemblyNames(DependencyContext.Default).Select(y => Assembly.Load(new AssemblyName(y.Name))));

            assemblys = assemblys.Where(x => !x.IsDynamic).ToArray();

            var types = assemblys.Select(x => x.GetType());
            types = interfaceTypes.Except(types);
            foreach (var t in types)
            {
                assemblys = assemblys.Append(t.Assembly);
            }
            var trees = interfaceTypes.Select(p => GeneratorProxyTree(p)).ToList();
            var stream = CompliationDispatch.ComplieClientProxy(trees,
                assemblys
                    .Select(a => MetadataReference.CreateFromFile(a.Location))
                    .Concat(new[]
                    {
                        MetadataReference.CreateFromFile(typeof(Task).GetTypeInfo().Assembly.Location)
                    }),
                _logger);

            using (stream)
            {
                var assembly = AssemblyLoadContext.Default.LoadFromStream(stream);
                return assembly.GetExportedTypes();
            }
        }

        /// <summary>
        /// 生成代理类的语法节点树
        /// </summary>
        /// <param name="interfaceType"></param>
        /// <returns></returns>
        public SyntaxTree GeneratorProxyTree(Type interfaceType)
        {
            InitializeUsings();
            var className = interfaceType.Name.StartsWith("I") ? interfaceType.Name.Substring(1) : interfaceType.Name;
            className += _proxyClassNameFormat;
            var members = new List<MemberDeclarationSyntax>()
            {
                BuildConstructorDeclaration(className)
            };
            members.AddRange(BuildMethodDeclarations(interfaceType.GetMethods()));
            return SyntaxFactory.CompilationUnit()
                .WithMembers(
                    SyntaxFactory.SingletonList<MemberDeclarationSyntax>(
                        SyntaxFactory.NamespaceDeclaration(
                            SyntaxFactory.QualifiedName(
                                SyntaxFactory.IdentifierName("Athena"),
                                SyntaxFactory.IdentifierName("ClientProxys")))
                .WithMembers(
                    SyntaxFactory.SingletonList<MemberDeclarationSyntax>(
                        SyntaxFactory.ClassDeclaration(className)
                            .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
                            .WithBaseList(
                                SyntaxFactory.BaseList(
                                    SyntaxFactory.SeparatedList<BaseTypeSyntax>(
                                        new SyntaxNodeOrToken[]
                                        {
                                            SyntaxFactory.SimpleBaseType(SyntaxFactory.IdentifierName(nameof(ServiceProxyAbstractClient))),
                                            SyntaxFactory.Token(SyntaxKind.CommaToken),
                                            SyntaxFactory.SimpleBaseType(BuildIdentifierName(interfaceType))
                                        })))
                            .WithMembers(SyntaxFactory.List(members))))))
                .WithUsings(BuildUsings())
                .NormalizeWhitespace()
                .SyntaxTree;
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }

        private IEnumerable<MemberDeclarationSyntax> BuildMethodDeclarations(IEnumerable<MethodInfo> methodInfos)
        {
            var arrayMethodInfo = methodInfos.ToArray();
            return arrayMethodInfo.Select(BuildMethodDeclaration).ToArray();
        }

        /// <summary>
        /// 构建方法
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <returns></returns>
        private MemberDeclarationSyntax BuildMethodDeclaration(MethodInfo methodInfo)
        {
            var correlationId = _serviceCorrelationIdGenerator.GenerateCorrelationId(methodInfo);

            //构建返回类型语法节点
            var returnTypeSyntax = BuildTypeSyntax(methodInfo.ReturnType);

            var parameterSyntaxNodeList = new List<SyntaxNodeOrToken>();
            var parameterDeclarationSyntaxNodeList = new List<SyntaxNodeOrToken>();

            //构建方法的入参语法节点
            foreach (var parameter in methodInfo.GetParameters())
            {
                var singleParameterDeclarationSyntaxNode = SyntaxFactory
                    .Parameter(SyntaxFactory.Identifier(parameter.Name))
                    .WithType(BuildTypeSyntax(parameter.ParameterType));
                parameterDeclarationSyntaxNodeList.Add(singleParameterDeclarationSyntaxNode);
                parameterDeclarationSyntaxNodeList.Add(SyntaxFactory.Token(SyntaxKind.CommaToken));


                parameterSyntaxNodeList.Add(SyntaxFactory.InitializerExpression(
                    SyntaxKind.ComplexElementInitializerExpression,
                    SyntaxFactory.SeparatedList<ExpressionSyntax>(
                        new SyntaxNodeOrToken[]{
                            SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression,SyntaxFactory.Literal(parameter.Name)),
                           SyntaxFactory.Token(SyntaxKind.CommaToken),SyntaxFactory.IdentifierName(parameter.Name)})));

                parameterSyntaxNodeList.Add(SyntaxFactory.Token(SyntaxKind.CommaToken));
            }
            if (parameterSyntaxNodeList.Any())
            {
                parameterDeclarationSyntaxNodeList.RemoveAt(parameterDeclarationSyntaxNodeList.Count - 1);
                parameterSyntaxNodeList.RemoveAt(parameterSyntaxNodeList.Count - 1);
            }

            var methodDeclaration = SyntaxFactory.MethodDeclaration(returnTypeSyntax,
                SyntaxFactory.Identifier(methodInfo.Name))
                .WithModifiers(SyntaxFactory.TokenList(
                    SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                    SyntaxFactory.Token(SyntaxKind.AsyncKeyword)))
                .WithParameterList(SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList<ParameterSyntax>(parameterDeclarationSyntaxNodeList)));

            ExpressionSyntax expressionSyntax;
            StatementSyntax statementSyntax;

            if (methodInfo.ReturnType != typeof(Task))
            {
                expressionSyntax = SyntaxFactory.GenericName(
                    SyntaxFactory.Identifier("InvokeAsync")).WithTypeArgumentList(((GenericNameSyntax)returnTypeSyntax).TypeArgumentList);
            }
            else
            {
                expressionSyntax = SyntaxFactory.IdentifierName("InvokeAsync");
            }


            var dictionarySyntaxNode = SyntaxFactory
                .GenericName(BuildIdentifier(typeof(Dictionary<,>)))
                .WithTypeArgumentList(SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList<TypeSyntax>(
                    new SyntaxNodeOrToken[]{
                        SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)),
                        SyntaxFactory.Token(SyntaxKind.CommaToken),
                        SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword))
                    })));

            var dictionaryObjectCreationExpression = SyntaxFactory.ObjectCreationExpression(dictionarySyntaxNode).WithInitializer(
                SyntaxFactory.InitializerExpression(
                        SyntaxKind.CollectionInitializerExpression,
                        SyntaxFactory.SeparatedList<ExpressionSyntax>(parameterSyntaxNodeList)));

            var expressionArgumensSyntaxNode = new SyntaxNodeOrToken[] {
                SyntaxFactory.Argument(dictionaryObjectCreationExpression),
                SyntaxFactory.Token(SyntaxKind.CommaToken),
                SyntaxFactory.Argument(SyntaxFactory.LiteralExpression( SyntaxKind.StringLiteralExpression,SyntaxFactory.Literal(correlationId)))
            };
            var expressionArgumentList = SyntaxFactory.SeparatedList<ArgumentSyntax>(expressionArgumensSyntaxNode);

            expressionSyntax = SyntaxFactory.AwaitExpression(SyntaxFactory.InvocationExpression(expressionSyntax).WithArgumentList(SyntaxFactory.ArgumentList(expressionArgumentList)));

            if (methodInfo.ReturnType != typeof(Task))
            {
                statementSyntax = SyntaxFactory.ReturnStatement(expressionSyntax);
            }
            else
            {
                statementSyntax = SyntaxFactory.ExpressionStatement(expressionSyntax);
            }

            methodDeclaration = methodDeclaration.WithBody(
                SyntaxFactory.Block(SyntaxFactory.SingletonList(statementSyntax)));

            return methodDeclaration;
        }

        /// <summary>
        /// 构建构造函数语法节点
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        private ConstructorDeclarationSyntax BuildConstructorDeclaration(string className)
        {
            //构建类型名称
            var classIdentifier = SyntaxFactory.Identifier(className);

            //构建访问标识为Public
            var classTokenList = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword));

            //构建构造函数的参数语法节点 
            var parameterListNodeOrToken = new SyntaxNodeOrToken[] {
                 SyntaxFactory.Parameter(SyntaxFactory.Identifier("rpcInvokeService")).WithType(SyntaxFactory.IdentifierName(typeof(IRpcInvokeService).Name)),
                 SyntaxFactory.Token(SyntaxKind.CommaToken),
                 SyntaxFactory.Parameter(SyntaxFactory.Identifier("typeConvertibleService")).WithType(SyntaxFactory.IdentifierName(typeof(ITypeConvertibleService).Name))
            };
            var parameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList<ParameterSyntax>(parameterListNodeOrToken));

            //构建父类的构造函数所需的参数语法节点（继承于ServiceProxyAbstractClient)
            var argumentNodeOrToken = new SyntaxNodeOrToken[] {
                SyntaxFactory.Argument(SyntaxFactory.IdentifierName("rpcInvokeService")),
                SyntaxFactory.Token(SyntaxKind.CommaToken),
                SyntaxFactory.Argument(SyntaxFactory.IdentifierName("typeConvertibleService")),
            };
            var classArgumentList = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList<ArgumentSyntax>(argumentNodeOrToken));

            var classInitializer = SyntaxFactory.ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, classArgumentList);

            return SyntaxFactory.ConstructorDeclaration(classIdentifier)
                   .WithModifiers(classTokenList)
                   .WithParameterList(parameterList)
                   .WithInitializer(classInitializer)
                   .WithBody(SyntaxFactory.Block());
        }

        private SyntaxToken BuildIdentifier(Type type)
        {
            AddUsing(type.Namespace);
            var name = type.Name;
            if (type.IsGenericType)
            {
                name = type.Name.Substring(0, type.Name.IndexOf('`'));
            }
            return SyntaxFactory.Identifier(name);
        }

        private IdentifierNameSyntax BuildIdentifierName(Type type)
        {
            AddUsing(type.Namespace);
            return SyntaxFactory.IdentifierName(type.Name);
        }

        /// <summary>
        /// 构建类型语法节点
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private TypeSyntax BuildTypeSyntax(Type type)
        {
            if (type == null)
            {
                return null;
            }

            AddUsing(type.Namespace);
            //非泛型。
            if (!type.IsGenericType)
            {
                return BuildIdentifierName(type);
            }

            var list = new List<SyntaxNodeOrToken>();

            //泛型 多个类型
            foreach (var genericTypeArgument in type.GenericTypeArguments)
            {
                list.Add(genericTypeArgument.IsGenericType
                    ? BuildTypeSyntax(genericTypeArgument) //泛型嵌套泛型
                    : BuildIdentifierName(genericTypeArgument));
                list.Add(SyntaxFactory.Token(SyntaxKind.CommaToken));
            }

            var array = list.Take(list.Count - 1).ToArray();
            var typeArgumentListSyntax = SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList<TypeSyntax>(array));

            return SyntaxFactory.GenericName(type.Name.Substring(0, type.Name.IndexOf('`'))) //泛型名称为：Class`T=Class<T>
                .WithTypeArgumentList(typeArgumentListSyntax);
        }

        /// <summary>
        /// 添加类型所需命名空间
        /// </summary>
        /// <param name="usingString"></param>
        private void AddUsing(string usingString)
        {
            if (!_namespaceCache.Contains(usingString))
            {
                _namespaceCache.Add(usingString);
            }
        }

        private void InitializeUsings()
        {
            _namespaceCache = new SortedSet<string>();
            if (!_namespaceCache.Contains(typeof(ITypeConvertibleService).Namespace))
            {
                _namespaceCache.Add(typeof(ITypeConvertibleService).Namespace);
            }
            if (!_namespaceCache.Contains(typeof(IRpcInvokeService).Namespace))
            {
                _namespaceCache.Add(typeof(IRpcInvokeService).Namespace);
            }
            if (!_namespaceCache.Contains(typeof(ServiceProxyAbstractClient).Namespace))
            {
                _namespaceCache.Add(typeof(ServiceProxyAbstractClient).Namespace);
            }
        }


        /// <summary>
        /// 构建引用命名空间语法节点
        /// </summary>
        /// <returns></returns>
        private SyntaxList<UsingDirectiveSyntax> BuildUsings()
        {
            var usingUsingDirective = new List<UsingDirectiveSyntax>();
            foreach (var item in _namespaceCache)
            {
                usingUsingDirective.Add(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(item)));
            }
            return SyntaxFactory.List(usingUsingDirective);
        }

        #region 废弃
        private QualifiedNameSyntax BuildQualifiedNameSyntax1(Type type)
        {
            AddUsing(type.Namespace);
            return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName(""), SyntaxFactory.IdentifierName(type.Name));
            var fullName = type.Namespace + "." + type.Name;
            return BuildQualifiedNameSyntax1(fullName);
        }

        private QualifiedNameSyntax BuildQualifiedNameSyntax1(string fullName)
        {
            //AddUsing(type.Namespace);
            return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName(""), SyntaxFactory.IdentifierName(fullName));

            return BuildQualifiedNameSyntax1(fullName.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries));
        }

        private QualifiedNameSyntax BuildQualifiedNameSyntax1(IReadOnlyCollection<string> names)
        {
            var ids = names.Select(SyntaxFactory.IdentifierName).ToArray();

            var index = 0;
            QualifiedNameSyntax left = null;
            while (index + 1 < names.Count)
            {
                left = left == null ? SyntaxFactory.QualifiedName(ids[index], ids[index + 1]) : SyntaxFactory.QualifiedName(left, ids[index + 1]);
                index++;
            }
            return left;
        }

        #endregion
    }
}
